#include "platform/opengl/opengl_texture.hpp"

#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>

namespace Xen {

/**********************
 * OpenGLTexture2D2DBase
 *********************/

void OpenGLTexture2DBase::Bind(uint32_t slot) const {
    GLCall(glActiveTexture(GL_TEXTURE0 + slot));
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
}

void OpenGLTexture2DBase::Unbind() const {
    GLCall(glBindTexture(GL_TEXTURE_2D, 0));
}

/**********************
 * OpenGLColorTexture2D
 *********************/

OpenGLColorTexture2D::OpenGLColorTexture2D(const std::string &path) {
    int w, h, channels;
    stbi_set_flip_vertically_on_load(true);
    void* pixels = stbi_load(path.c_str(), &w, &h, &channels, 0);
    XEN_CORE_ASSERT(pixels, "file can't load: " + path);

    if (channels == 1) {
        internal_format_ = GL_RED;
        data_format_ = GL_RED;
    } else if (channels == 3) {
        internal_format_ = GL_RGB8;
        data_format_ = GL_RGB;
    } else if (channels == 4) {
        internal_format_ = GL_RGBA8;
        data_format_ = GL_RGBA;
    }
    XEN_CORE_ASSERT(internal_format_ && data_format_, "Invalid internal_format or data_format");

    w_ = w;
    h_ = h;

    GLCall(glGenTextures(1, &tex_));
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));

    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, internal_format_, w, h, 0, data_format_, GL_UNSIGNED_BYTE, (const void*)pixels));
    GLCall(glGenerateMipmap(GL_TEXTURE_2D));

    stbi_image_free(pixels);
}

OpenGLColorTexture2D::OpenGLColorTexture2D(uint32_t w, uint32_t h) {
    GLCall(glGenTextures(1, &tex_));
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
    GLCall(glBindTexture(GL_TEXTURE_2D, 0));

    w_ = w;
    h_ = h;
}

void OpenGLColorTexture2D::SetData(const void* data) {
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
    GLCall(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, GetWidth(), GetHeight(), data_format_, GL_UNSIGNED_BYTE, data));
    GLCall(glBindTexture(GL_TEXTURE_2D, 0));
}

OpenGLColorTexture2D::~OpenGLColorTexture2D() {
    GLCall(glDeleteTextures(1, &tex_));
}

/**************************
 * OpenGLDepthTexture2D
 **************************/

OpenGLDepthTexture2D::OpenGLDepthTexture2D(uint32_t w, uint32_t h) {
    GLCall(glGenTextures(1, &tex_));
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL));
    GLCall(glBindTexture(GL_TEXTURE_2D, 0));

    w_ = w;
    h_ = h;
}

OpenGLDepthTexture2D::~OpenGLDepthTexture2D() {
    GLCall(glDeleteTextures(1, &tex_));
}

void OpenGLDepthTexture2D::SetData(const void *data) {
    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w_, h_, 0, GL_DEPTH_COMPONENT, GL_FLOAT, data));
}

/**************************
 * OpenGLDepthStencilTexture2D
 **************************/
OpenGLDepthStencilTexture2D::OpenGLDepthStencilTexture2D(uint32_t w, uint32_t h) {
    GLCall(glGenTextures(1, &tex_));
    GLCall(glBindTexture(GL_TEXTURE_2D, tex_));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
    GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr));
    GLCall(glBindTexture(GL_TEXTURE_2D, 0));

    w_ = w;
    h_ = h;
}

OpenGLDepthStencilTexture2D::~OpenGLDepthStencilTexture2D() {
    GLCall(glDeleteTextures(1, &tex_));
}

void OpenGLDepthStencilTexture2D::SetData(const void *data) {
    GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w_, h_, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, data));
}
}
